#-*-coding:utf-8-*-
import numpy as np
import chainer
from chainer import Chain,report
from chainer import Parameter,Variable
from chainer import links as L
from chainer import functions as F

HIDDEN_SIZE = 100


# 定义网络
class MyNetwork(Chain):
    def __init__(self,input_size):
        super(MyNetwork, self).__init__()
        with self.init_scope():
            self.linear = L.Linear(input_size, HIDDEN_SIZE)
            self.fm = FactorizationMachine(HIDDEN_SIZE, 5)
    
    def forward(self,x):
        x = Variable(x.astype('float32'))
        x = self.linear(x)
        x =F.dropout(x,ratio=.5)
        x = self.fm(x)
        return x 

    def __call__(self,x,t):
        x = self.forward(x)
        t = Variable(t.reshape(x.shape))
        loss = F.mean_squared_error(x,t)
        report({'loss':loss},self)
        return loss
    def predict(self, x):
        with chainer.using_config('train', False):
            with chainer.no_backprop_mode():
                return self.forward(x)





#因式分解机
class FactorizationMachine(Chain):

    def __init__(self,input_features, factors):
        super(FactorizationMachine, self).__init__()
        with self.init_scope():
            self.input_features, self.factors = input_features, factors
            #线性单元，线性回归
            self.linear = L.Linear(self.input_features, 1)
            #因式分解机的最后一项
            self.second_order = SecondOrderInteraction(self.input_features,
                                                   self.factors)
    def __call__(self, x):
        linear = self.linear(x)
        interaction = self.second_order(x)
        res = linear + interaction
        return res 

class SecondOrderInteraction(Chain):
    def __init__(self,n_feats,n_factors):
        super(SecondOrderInteraction,self).__init__()
        with self.init_scope():
            #输入数据的列数
            self.n_feats = n_feats
            #v的宽度，特征数
            self.n_factors = n_factors
            self.v = Parameter(((np.random.rand(n_feats,n_factors)-0.5)/50).astype('float32'))
    def __call__(self,x):
        self.batch_size = x.data.shape[0]
        #input width
        self.n_feats = x.data.shape[-1]
        # v's length 
        self.n_factors = self.v.data.shape[-1]
        #output init
        output = Variable(np.zeros((self.batch_size,self.n_feats,self.n_feats),dtype='float32'))
        res = Variable(np.zeros((self.batch_size,1),dtype='float32'))
        #Computes the matrix multiplication of two arrays
        #x.transpose()is x's transpose 转置
        # 计算点积矩阵 向量的点积矩阵 登录矩阵和它的矩阵转置相乘 可推导，
        all_interactions = F.matmul(self.v,self.v.T)
        # all_interactions
        for b in range(self.batch_size):
            for i in range(self.n_feats):
                for j in range(i+1,self.n_feats):
                    #vi*vj*xi*xj 向量先做点积然后与xi xj相乘
                    output.data[b][i][j] = all_interactions.data[i][j] * x.data[b,i] * x.data[b,j]
            res.data[b]=F.sum(output.data[b]).data
        #print res.shape
        return res 